<?php
/**
 * @desc pagination class file
 * @author nana
 * @date 2011
 */

class Pagination
{
	const PAGE_SIZE          = 10;
	const OPTION_PREV        = 'prev';
	const OPTION_BACK_10     = 'back10';
	const OPTION_BACK_100    = 'back100';
	const OPTION_FIRST       = 'first';
	const OPTION_NEXT        = 'next';
	const OPTION_FORWARD_10  = 'forward10';
	const OPTION_FORWARD_100 = 'forward100';
	const OPTION_LAST        = 'last';

	protected $showPageCount        = 10;//page size in pagination bar
    protected $pageSize             = 10;
	protected $currentPage          = 1;
	
	protected $pageVariable         = 'page';
	protected $pageLinkSeparator    = ' ';
	protected $separator            = '...';
	protected $morePageSeparator     = '...';
    
    protected $isShowFirstLast      = false;
    protected $isShowPrevNext       = false;
	protected $isShowForwardBack10  = false;
    protected $isShowForwardBack100 = false;
	protected $useSequence          = false;

	protected $baseUrl;
    protected $totalRows;
    protected $totalPages;
	
    //can not change this array keys order
    protected $optionLinks = array(
							   self::OPTION_PREV 	    => '上一页',
		                       self::OPTION_BACK_10     => '后10页',
							   self::OPTION_BACK_100    => '后100页',
							   self::OPTION_FIRST	    => '第一页',
                               self::OPTION_NEXT 	    => '下一页',
                               self::OPTION_FORWARD_10  => '前10页',
							   self::OPTION_FORWARD_100 => '前100页',
							   self::OPTION_LAST	    => '最后一页',      
								);

	protected static $callPrefixes = array('get', 'set', 'isshow');

	public function __construct()
	{
		$this->setBaseUrl(self::getURLQuery());
	}
		
	public function __call($method, $args)
	{
	//	$method = strtolower($method);
        list($cmd, $needle) = $this->resolveCall($method);

        return $this->$cmd($needle, $args);		
	}

    protected function resolveCall($method)
    {
        $prefixes = &self::$callPrefixes;
        $method_i = strtolower($method);

        foreach($prefixes as $prefix)
        {
            if(substr($method_i, 0, strlen($prefix)) == $prefix) return array('retrieve'.ucwords($prefix), substr($method, strlen($prefix)));
        }

        throw new Exception('invalid method call to '.$method);
    }

    protected function retrieveSet($needle, $args)
	{
        $attr_name = strtolower($needle[0]).substr($needle, 1);
		$this->$attr_name = (int)trim($args[0]);
    }

    protected function retrieveIsShow($needle, $args)
	{
		$this->$needle = ($args[0] == true) ? true : false;
    }
   
    public function retrievePages() 
	{
        if($this->pageSize < 1)  
		{
			$this->pageSize = self::PAGE_SIZE;
		}

        $this->totalPages = (int)ceil($this->totalRows / $this->pageSize);

        if($this->currentPage > $this->totalPages) 
		{
			$this->currentPage = $this->totalPages;
		}

        if($this->currentPage < 1) 
		{
			$this->currentPage = 1;
		}

        return true;
    }

    public function toString()
	{
        $this->retrievePages();
        $pages = $this->buildPages();

        if(!is_array($pages)) 
		{
			return "";
		}

		$this->addOptionPages($pages);

        return implode($this->pageLinkSeparator, $pages);
    }

	public function toSimpleString() 
	{
		$this->retrievePages();
		$range = $this->getShowPageRange();
		
		for ($i = $range[0]; $i < $range[1]; $i++) 
		{
			$pages[] = $this->buildPageLink($i + 1);
		}
		
		if ($this->isShowPrev())
		{
			array_unshift($pages, $this->getPrevLink());
		}
		if ($this->isShowNext())
		{
			array_push($pages, $this->getNextLink());
		}

		return implode($this->pageLinkSeparator, $pages);;
	}

	private function addOptionPages(&$pages)
	{
		$i = 0;
		$prefixs = array_keys($this->optionLinks);
        foreach($prefixs as $prefix)
		{
            if($this->isShowOption($prefix)) 
			{
				if ($i > 3)
				{	
					array_push($pages, $this->getOptionLink($prefix));
				}
				else
				{
					array_unshift($pages, $this->getOptionLink($prefix));
				}
			}

			$i++;
        }
		
		return true;
	}

	private function getStart($v)
	{
	    if(!$this->useSequence) 
		{
			return $v;
		}

	    return ($v * $this->pageSize) - $this->pageSize + 1;
	}
    

	private function buildPageLink($page_num, $text = null, $use_b = false, $css_class = 'page')
	{
		$html = "";
		$page_num = (int)$page_num;

	    if($page_num < 1) 
		{
			return $html;
		}

		$class = '';

	    if(null === $text) 
		{
	        $text  = $page_num;
			$class = ' class=" '.$css_class.'" ';
	    } 
		else 
		{
	        $class = ' class="'.$css_class.' link" ';
	    }    
	    
	    if ($page_num == $this->currentPage) 
		{
		    $class= ' class="'.$css_class.' current" ';
		}
		
		if ($use_b && $this->currentPage == $page_num)
		{
			$html = '<b class="'.$class.'>"'.$text.'</b>';
		}
		else
		{
			$html = '<a page-num='.$this->getStart($page_num).' href="'.$this->baseUrl.$this->getStart($page_num ).'"  title="'.$text.'"'.$class.'>'.$text.'</a>';
		}
			
        return $html;
	}
	
    public function setBaseUrl($url) 
	{
        return $this->baseUrl = $this->parseToBaseUrl($url);
    }

    protected function parseToBaseUrl($in_url) 
	{
		$in_url .= preg_match('/\?/i', $in_url) ? '&'.$this->pageVariable.'=' : '?'.$this->pageVariable.'=';
			
        return $in_url;
    }

	public static function getURLQuery($page_variable = 'page') 
	{
		$url       = isset($_SERVER["REQUEST_URI"]) ? $_SERVER["REQUEST_URI"] : "";
        $url       = self::filterSpecialChar($url);
		$parse_url = parse_url($url);
		$query     = isset($parse_url["query"]) ? $parse_url["query"] : null;
		
		if($query) 
		{
			//$page      = isset($_GET[$page_variable]) ? $_GET[$page_variable] : "";
            $page      = NICED_Request::loadParamFromVar($page_variable, 0, "posint", "REQUEST", true);
			$query     = self::stripURLPageParam($query, $page_variable, $page);
			$url       = str_replace($parse_url["query"], $query, $url);	
		} 

		return $url;
	}

    public static function filterSpecialChar($str)
    {
        //return str_replace(array('<', '>', '"', "'", '{', '(', '&', '%'), '', $str);
        return str_replace(array('<', '>', '"', "'", '{', '(' /**, '&', '%'*/), '', $str);
    }

	public static function stripURLPageParam($url, $key = 'page', $value = 0)
	{
		$value = urlencode($value);
        $url = preg_replace("/(^|&|)$key=$value/", "", $url);
		
		return $url;
	}

    public static function appendToURL($in_url, $str) 
	{
		$str = ltrim($str, "&");
		$in_url .= preg_match('/\?/i', $in_url)
            ? '&'.$str
            : '?'.$str;
			
        return $in_url;
    }


	private function getShowPageRange()  
	{
		$half = ceil($this->showPageCount / 2);
		$start = $this->currentPage > $half ? max(min($this->currentPage - $half, ($this->totalPages - $this->showPageCount)), 0) : 0;
		$end   = $this->currentPage > $half ? min($this->currentPage + $half, $this->totalPages) : min($this->showPageCount, $this->totalPages);
		
		if ($this->currentPage - $start > $half)
		{
			$start = $this->currentPage - $half;
		}

		return array($start, $end);
	}

    private function buildPages() 
	{
        if ($this->totalPages > 20)
        {
			$pages = $this->buildMorePages();
		} 
		else if ($this->totalPages > 10)
		{
			$pages = $this->buildLessPages();
		}
		else
		{
			$pages = array();
			for($i = 1; $i < $this->totalPages + 1; $i++)
			{
				$pages[] = $this->buildPageLink($i);
			}
		}
				
		return $pages;
	}

	private function buildMorePages()
	{
		$pages = array();
		$page_count_max = ($this->currentPage < 2) ? 6 : 2;
		
		for($i = 1; $i < $page_count_max + 1; $i++)
		{
			$pages[] = $this->buildPageLink($i);
		}	

		if ($this->currentPage < $this->totalPages)
		{
			$pages[] = ($this->currentPage > 5) ? $this->morePageSeparator : $this->separator;
					
			$page_count_min = ($this->currentPage > 4) ? $this->currentPage : 5;
			$page_count_max = ($this->currentPage < $this->totalPages - 4) ? $this->currentPage : $this->totalPages - 4;
					
			if ($page_count_max + 6 >= $this->totalPages - 2) 
			{
				$page_count_middle = $this->totalPages - 2;
			}
			else 
			{
				$page_count_middle = $page_count_max + 6;
			}

			for($i = $page_count_min - 2; $i < $page_count_middle; $i++)
			{
				$pages[] = $this->buildPageLink($i);
			}
			
			$pages[] = ($this->currentPage < ($this->totalPages - 3)) ? $this->morePageSeparator : $this->separator;
		}
		else
		{
			$pages[] = $this->separator;
		}
		
		for($i = $this->totalPages - 2; $i < $this->totalPages + 1; $i++)
		{
			$pages[] = $this->buildPageLink($i);
		}

		return $pages;
	}

	private function buildLessPages()
	{
		$pages = array();
		$page_count_max = ($this->totalPages > 3) ? 3 : $this->totalPages;
					
		for($i = 1; $i < $page_count_max + 1; $i++)
		{
			$pages[] = $this->buildPageLink($i);
		}
		
		if ($this->totalPages > 3)
		{
			if ($this->currentPage > 1 && $this->currentPage < $this->totalPages)
			{
				$pages[] = ($this->currentPage > 5) ? $this->morePageSeparator : $this->separator;
				
				$page_count_min = ($this->currentPage > 4) ? $this->currentPage : 5;
				$page_count_max = ($this->currentPage < $this->totalPages - 4) ? $this->currentPage : $this->totalPages - 4;
	
					
				for($i = $page_count_min - 1; $i < $page_count_max + 2; $i++)
				{
					$pages[] = $this->buildPageLink($i);
				}

				$pages[] = ($this->currentPage < $this->totalPages - 4) ? $this->morePageSeparator : $this->separator;
			}
			else
			{
				$pages[] = $this->separator;
			}
	
			for($i = $this->totalPages - 2; $i < $this->totalPages + 1; $i++)
			{
				$pages[] = $this->buildPageLink($i);
			}
		}	

		return $pages;
	}

	private function getOptionLink($type)
	{
		switch ($type)
		{
			case self::OPTION_FIRST : 
			{
				return $this->buildPageLink(1, $this->optionLinks[self::OPTION_FIRST]);
			}
			case self::OPTION_LAST :
			{
				return $this->buildPageLink($this->totalPages, $this->optionLinks[self::OPTION_LAST]);
			}
			case self::OPTION_PREV : 
			{
				return $this->buildPageLink($this->currentPage - 1,  $this->optionLinks[self::OPTION_PREV]);
			}
			case self::OPTION_NEXT :
			{
				return $this->buildPageLink($this->currentPage + 1 , $this->optionLinks[self::OPTION_NEXT]);
			}
			case self::OPTION_BACK_10 :
			{
				return $this->buildPageLink($this->currentPage - 10,  $this->optionLinks[self::OPTION_BACK_10]);
			}
			case self::OPTION_FORWARD_10 :
			{
				return $this->buildPageLink($this->currentPage + 10, $this->optionLinks[self::OPTION_FORWARD_10]);
			}
			case self::OPTION_BACK_100 :
			{
				return $this->buildPageLink($this->currentPage - 100, $this->optionLinks[self::OPTION_BACK_100]);
			}
			case self::OPTION_FORWARD_100 :
			{
				return $this->buildPageLink($this->currentPage + 100, $this->optionLinks[self::OPTION_FORWARD_100]);
			}			
		}
	}

	private function isShowOption($type)
	{
		switch ($type)
		{
			case self::OPTION_FIRST : 
			{
				return ($this->isShowFirstLast && $this->currentPage > 1 && $this->totalPages > 100) ? true : false;
			}
			case self::OPTION_LAST :
			{
				return ($this->isShowFirstLast && $this->currentPage < $this->totalPages && $this->totalPages > 100) ? true : false;
			}
			case self::OPTION_PREV : 
			{
				return ($this->isShowPrevNext && $this->currentPage > 1) ? true : false;
			}
			case self::OPTION_NEXT :
			{
				return ($this->isShowPrevNext && $this->currentPage < $this->totalPages) ? true : false;
			}
			case self::OPTION_BACK_10 :
			{
				return ($this->isShowForwardBack10 && $this->totalPages > 20 && $this->currentPage > 10) ? true : false;
			}
			case self::OPTION_FORWARD_10 :
			{
				return ($this->isShowForwardBack10 && $this->totalPages > 20  && $this->currentPage < ($this->totalPages - 10)) ? true : false;
			}
			case self::OPTION_BACK_100 :
			{
				return ($this->isShowForwardBack100 && $this->totalPages > 100 && $this->currentPage > 100) ? true : false;
			}
			case self::OPTION_FORWARD_100 :
			{
				return ($this->isShowForwardBack100 && $this->totalPages > 100 && $this->currentPage < ($this->totalPages - 100)) ? true : false;
			}			
		}	
	}

} //end class

if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME']))
{
	try
	{
			$p = new Pagination();
			$p->setBaseUrl(Pagination::getURLQuery());
			$p->setTotalRows(1000);
			$p->setPageSize(10);
			$p->setCurrentPage($_GET['page']);
			$p->setIsShowPrevNext(true);
			$p->setIsShowForwardBack10(true);
			$pagination = $p->toString();//or $pagination = $p->toSimpleString()
			
			var_dump($pagination);
	}
	catch (Exception $e)
	{
		echo $e;
	}
}
